RStorage = game.ReplicatedStorage;
local sharedLibs = RStorage.SharedLibs;
local oo = require(sharedLibs.BaseLib);
Debugger = require(sharedLibs.Debugger);

_Clamp = math.clamp;

-----
local AABB = oo.class();

function AABB.FromPosSize(pos, size)
	local size = size * 0.5;
	local x = pos.X; local y = pos.Y; local z = pos.Z;
	
	local aabb = AABB.new(-size.X + x
		, size.X + x
		, -size.Y + y
		, size.Y + y	
		, -size.Z + z
		, size.Z + z);
	return aabb;
end

function AABB.FromSize(size)
	local size = size * 0.5;
	
	return AABB.new(-size.X
		, size.X
		, -size.Y
		, size.Y		
		, -size.Z
		, size.Z);		
end

function AABB.FromPart(part)
	local ori = part.Orientation;
	if Debugger.IsStudio then
		if not (ori.X == 0) and (ori.Y == 0) and (ori.Z == 0) then
			warn("AABB.FromPart: "..part.Name.."'s orientation is not zero, the AABB generated may be in correct!");
		end
	end
	local pos = part.Position;
	local halfSize = part.Size / 2;
	
	return AABB.new(pos.X - halfSize.X
		, pos.X + halfSize.X
		, pos.Y - halfSize.Y
		, pos.Y + halfSize.Y		
		, pos.Z - halfSize.Z
		, pos.Z + halfSize.Z);
end

------------
function AABB:constructor(minX, maxX, minY, maxY, minZ, maxZ)
	if Debugger.IsStudio then
		assert(minX <= maxX and minY <= maxY and minZ <= maxZ);
	else
		if minX > maxX then local t = minX; minX = maxX; maxX = t; end
		if minY > maxY then local t = minY; minY = maxY; maxY = t; end
		if minZ > maxZ then local t = minZ; minZ = maxZ; maxZ = t; end
	end

	self.minX = minX;
	self.maxX = maxX;
	
	self.minY = minY;
	self.maxY = maxY;
	
	self.minZ = minZ;
	self.maxZ = maxZ;
end

function AABB:Merge(other)
	self.minX = math.min(self.minX, other.minX)
	self.maxX = math.max(self.maxX, other.maxX)
	self.minY = math.min(self.minY, other.minY)
	self.maxY = math.max(self.maxY, other.maxY)
	self.minZ = math.min(self.minZ, other.minZ)
	self.maxZ = math.max(self.maxZ, other.maxZ)
	return self;
end

function AABB:Intersect(other)
	self.minX = math.max(self.minX, other.minX)
	self.maxX = math.min(self.maxX, other.maxX)
	self.minY = math.max(self.minY, other.minY)
	self.maxY = math.min(self.maxY, other.maxY)
	self.minZ = math.max(self.minZ, other.minZ)
	self.maxZ = math.min(self.maxZ, other.maxZ)
	
	-- 如果两个AABB没有交集，则返回一个空的AABB
	if self.minX > self.maxX or self.minY > self.maxY or self.minZ > self.maxZ then
	   self.minX = 0
	   self.maxX = 0
	   self.minY = 0
	   self.maxY = 0
	   self.minZ = 0
	   self.maxZ = 0
	end
	return self;
end

--- 检查和另外一个aabb是否有重叠
function AABB:CheckIntersection(aabb)
    -- Check for overlap on X-axis
    if self.maxX < aabb.minX or aabb.maxX < self.minX then
        return false
    end    
    -- Check for overlap on Y-axis
    if self.maxY < aabb.minY or aabb.maxY < self.minY then
        return false
    end    
    -- Check for overlap on Z-axis
    if self.maxZ < aabb.minZ or aabb.maxZ < self.minZ then
        return false
    end    
    return true
end

function AABB:ClampPt(pt)
	local x = _Clamp(pt.X, self.minX, self.maxX);
	local y = _Clamp(pt.Y, self.minY, self.maxY);
	local z = _Clamp(pt.Z, self.minZ, self.maxZ);
	return Vector3.new(x, y, z);
end

function AABB:ClampPtXZ(pt)
	local x = _Clamp(pt.X, self.minX, self.maxX);
	local z = _Clamp(pt.Z, self.minZ, self.maxZ);
	return Vector3.new(x, pt.Y, z);
end

--- Check if pt is int AABB (ignore Y)
function AABB:CheckPtXZ(pt)
	if pt.X >= self.minX and pt.X <= self.maxX 
		and pt.Z >= self.minZ and pt.Z <= self.maxZ
	then
		return true;
	else
		return false;
	end
end

--- Check if pt is in AABB
function AABB:CheckPt(pt)
	if pt.X >= self.minX and pt.X <= self.maxX 
		and pt.Y >= self.minY and pt.Y <= self.maxY
		and pt.Z >= self.minZ and pt.Z <= self.maxZ
	then
		return true;
	else
		return false;
	end
end

function AABB:GetXZCenter(y)
	local x = (self.minX + self.maxX) / 2;
	local z = (self.minZ + self.maxZ) / 2;
	
	return Vector3.new(x, y, z);
end

function AABB:GetCenterPos()
	local x = (self.minX + self.maxX) / 2;
	local y = (self.minY + self.maxY) / 2;
	local z = (self.minZ + self.maxZ) / 2;
	
	return Vector3.new(x, y, z);
end

function AABB:GetSize()
	local x = self.maxX - self.minX;
	local y = self.maxY - self.minY;
	local z = self.maxZ - self.minZ;
	assert(x >= 0 and y >= 0 and z >= 0);
	return Vector3.new(x, y, z);
end

function AABB:GetLength()
	local ret = self.maxZ - self.minZ;
	assert(ret >= 0);
	return ret;
end

function AABB:GetWidth()
	local ret = self.maxX - self.minX;
	assert(ret >= 0);
	return ret;
end

function AABB:GetHeight()
	local ret = self.maxY - self.minY;
	assert(ret >= 0);
	return ret;
end

--- 返回XZ平面上的四个拐点, 拐点的Y使用传入的y
function AABB:GetXZConnerPts(y)
	local pt1 = Vector3.new(self.minX, y, self.minZ);
	local pt2 = Vector3.new(self.maxX, y, self.minZ);
	local pt3 = Vector3.new(self.minX, y, self.maxZ);
	local pt4 = Vector3.new(self.maxX, y, self.maxZ);
	return {pt1, pt2, pt3, pt4};
end

--- 返回XZ平面上的四个拐点, 拐点的Y使用传入的y
function AABB:GetXZEdgePts(y)
	local midX = (self.minX + self.maxX) / 2;
	local midZ = (self.minZ + self.maxZ) / 2;

	local pt1 = Vector3.new(self.minX, y, midZ);
	local pt2 = Vector3.new(self.maxX, y, midZ);
	local pt3 = Vector3.new(midX, y, self.minZ);
	local pt4 = Vector3.new(midX, y, self.maxZ);

	return {pt1, pt2, pt3, pt4};
end

--- 将AABB的边缘收缩x, y, z宽度. 
--- 注意: AABB的大小将缩小 2x, 2y, 2z
function AABB:Shrink(x, y, z)
	local newMinX = self.minX + x;
    local newMaxX = self.maxX - x;
    local newMinY = self.minY + y;
    local newMaxY = self.maxY - y;
    local newMinZ = self.minZ + z;
    local newMaxZ = self.maxZ - z;
	if newMaxX < newMinX then 
		local newX = (newMaxX + newMinX) / 2;
		newMaxX = newX; newMinX = newX;
	end
	if newMaxY < newMinY then 
		local newY = (newMaxY + newMinY) / 2;
		newMaxY = newY; newMinY = newY;
	end
	if newMaxZ < newMinZ then 
		local newZ = (newMaxZ + newMinZ) / 2;
		newMaxZ = newZ; newMinZ = newZ;
	end
	self.minX = newMinX;
	self.maxX = newMaxX;
	self.minY = newMinY;
	self.maxY = newMaxY;
	self.minZ = newMinZ;
	self.maxZ = newMaxZ;
end

--- 将AABB的边缘扩展x, y, z宽度. 
--- 注意: AABB的大小将放大 2x, 2y, 2z
function AABB:Expand(x, y, z)
	local newMinX = self.minX - x;
    local newMaxX = self.maxX + x;
    local newMinY = self.minY - y;
    local newMaxY = self.maxY + y;
    local newMinZ = self.minZ - z;
    local newMaxZ = self.maxZ + z;
	self.minX = newMinX;
	self.maxX = newMaxX;
	self.minY = newMinY;
	self.maxY = newMaxY;
	self.minZ = newMinZ;
	self.maxZ = newMaxZ;
end

--- 用offset平移AABB
function AABB:Offset(offset)
	local x = offset.X;
	local y = offset.Y;
	local z = offset.Z
	
	self.minX = self.minX + x;
	self.maxX = self.maxX + x;
	
	self.minY = self.minY + y;
	self.maxY = self.maxY + y;
	
	self.minZ = self.minZ + z;
	self.maxZ = self.maxZ + z;
end

function AABB:GetRandPtV3()
	return Vector3.new(self.minX + math.random() * (self.maxX - self.minX)
		, self.minY + math.random() * (self.maxY - self.minY)
		, self.minZ + math.random() * (self.maxZ - self.minZ));
end

function AABB:GetRandPtXZ(y)
	return Vector3.new(self.minX + math.random() * (self.maxX - self.minX)
		, y
		, self.minZ + math.random() * (self.maxZ - self.minZ));
end

--- 在AABB的靠近minX的边缘处随机取点
function AABB:GetMinXPtV3(range)
	y = y or 0;
	return Vector3.new(self.minX + math.random() * range
		, self.minY + math.random() * (self.maxY - self.minY)
		, self.minZ + math.random() * (self.maxZ - self.minZ));
end

--- 在AABB的靠近minX的边缘处随机取点
function AABB:GetMaxXPtV3(range)
	y = y or 0;
	return Vector3.new(self.maxX - math.random() * range
		, self.minY + math.random() * (self.maxY - self.minY)
		, self.minZ + math.random() * (self.maxZ - self.minZ));
end

return AABB;